11. Pandas-数据框内部操作

本章概要

  • 学习材料:含代码、日期、价格和行业字段的 DataFrame。
  • 本章任务:运行 lst-sort-valueslst-apply-demolst-map-demo,完成排序、逐行计算和逐元素映射。
  • 完成后你将得到:排序后的键序、apply 新列与 map 标签列。
  • 自我检查:用显式表达式复算一行并确认排序单调;索引错位或 dtype 不符时先检查原因。
  • 拓展练习:把操作步骤应用到另一份中国公司明细。

本章学习目标

  • 掌握 read_csv 关键参数的使用方法
  • 理解 lociloc 两种索引方式的区别
  • 学会按索引和按值对数据框排序
  • 掌握条件筛选与 query 查询方法
  • 学会使用 applymap 进行数据变换

数据清洗的价值

  • 数据清洗(Data Cleaning)是数据分析中最耗时的环节
  • 通常占据分析工作 60-80% 的时间
  • Pandas 提供了丰富的数据框内部操作方法

高质量数据的四个维度:

高质量数据的四个维度

维度 含义
完整性 无缺失值或合理处理
一致性 格式统一,单位一致
准确性 值在合理范围内
唯一性 无重复记录

运行前预测|平台任务:数据读取与基础操作

  • 输入预测:运行前先写出 dfTrade_daysstock_pricedf_asc 的业务含义、数据类型或取值范围,并判断哪一个输入最可能改变结果。
  • 结果预测:不展开答案,先预测将得到“2022年共有{Trade_days}个交易日”相关结果;同时写出方向、数量级或表格/图形结构。
  • 完成要求:能独立说明本任务从输入到“平台任务:数据读取与基础操作”结果的关键步骤,原样录入平台代码并得到可核对的运行结果。

⭐ 平台任务:数据读取与基础操作

展开完整代码(投影默认折叠)
# ⚠️ 平台原始代码 - 请原样输入至教学平台(注释除外),平台才会判定答案正确
import pandas as pd  # 导入Pandas数据分析库
#将第一列(date)设置为索引,只读取指定的列usecols
df=pd.read_csv("https://huoran.oss-cn-shenzhen.aliyuncs.com/20230306/csv/1632645776084066304.csv",index_col=0,usecols=['date','syzg','zgpa','zgjz']).dropna()

Trade_days=df.shape[0]  # 获取数据维度信息
print(f'2022年共有{Trade_days}个交易日')  # 输出2022年共有

stock_price=df.loc['2022-03-01','zgjz']  # 按标签索引提取数据
print(f'3月第一个交易日中国建筑价格为{stock_price}元')  # 输出3月第一个交易日中国建筑价格为

df_asc=df.sort_index(ascending=True)  # 按索引排序
print(df_asc.head(8))  # 输出前几行数据

任务复盘|平台任务:数据读取与基础操作

运行后核对:核对 dfTrade_daysstock_pricedf_asc 是否按预测参与运算,实际输出是否与预测一致;若不一致,先检查类型、单位、索引/字段和运算顺序。

拓展练习:把输入表替换为本地中国上市公司数据的同结构子集;指出必须保持的字段、数据类型和质量检查。

read_csv 关键参数解析

  • index_col=0:将第一列(date)设为行索引
    • 便于时间序列分析和日期检索
  • usecols=[...]:只加载指定列
    • 减少内存占用,提高读取速度
  • .dropna():链式操作,读取后立即删除缺失值
# 链式操作写法(推荐)
df = (pd.read_csv(url)
       .dropna()
       .sort_index())

数据框基础属性一览

Listing 1: 离线教学快照:查看DataFrame的常用属性
展开实现代码
import pandas as pd  # 导入Pandas用于构建和操作数据框

# 固定快照只承担索引教学,避免构建依赖失效的远程OSS链接
df = pd.DataFrame({  # 构建可离线重新运行的八个交易日教学快照
    'date': pd.to_datetime(['2022-03-01', '2022-03-02', '2022-03-03', '2022-03-04',
                            '2022-03-07', '2022-03-08', '2022-03-09', '2022-03-10']),
    'syzg': [27.52, 27.31, 26.76, 26.50, 25.98, 25.54, 24.92, 25.35],
    'zgpa': [51.08, 50.82, 50.12, 49.80, 48.95, 48.36, 47.90, 48.41],
    'zgjz': [5.39, 5.35, 5.30, 5.28, 5.20, 5.14, 5.09, 5.15]
}).set_index('date')  # 将交易日期设为索引以演示时间序列检索

# 数据框形状
print(f'数据框形状: {df.shape}')
print(f'  - 行数(交易日): {df.shape[0]}')
print(f'  - 列数(股票): {df.shape[1]}')
数据框形状: (8, 3)
  - 行数(交易日): 8
  - 列数(股票): 3

数据边界:该固定快照用于 lociloc、排序与筛选的离线教学,不用于收益率或投资结论;平台完整行情仍保留在受保护折叠块中。

查看索引与列名信息

Listing 2: 查看索引与列名信息
展开实现代码
# 索引信息
print(f'索引类型: {type(df.index)}')
print(f'索引范围: {df.index.min()}{df.index.max()}')

# 列名
print(f'\n列名(股票代码):')
print(df.columns.tolist())
# syzg(三一重工)、zgpa(中国平安)、zgjz(中国建筑)

# 数据类型
print(f'\n数据类型:')
print(df.dtypes)
索引类型: <class 'pandas.core.indexes.datetimes.DatetimeIndex'>
索引范围: 2022-03-01 00:00:00 到 2022-03-10 00:00:00

列名(股票代码):
['syzg', 'zgpa', 'zgjz']

数据类型:
syzg    float64
zgpa    float64
zgjz    float64
dtype: object

loc 标签索引:获取特定值

  • loc 使用标签(行名/列名)进行选择
  • 切片包含末端(与 Python 标准切片不同)
Listing 3: 使用loc获取特定日期的股价
# 获取特定日期的单个值
stock_price = df.loc['2022-03-01', 'zgjz']
print(f'3月第一个交易日中国建筑价格: {stock_price}元')
3月第一个交易日中国建筑价格: 5.39元

loc 标签索引:切片与多列选择

Listing 4: 使用loc进行切片与多列选择
# 选择日期范围 + 指定列
subset = df.loc[
    '2022-03-01':'2022-03-05',  # 行切片(包含两端)
    ['syzg', 'zgpa']            # 指定列名列表
]
print('3月1-5日, 三一重工和中国平安的价格:')
print(subset)
3月1-5日, 三一重工和中国平安的价格:
             syzg   zgpa
date                    
2022-03-01  27.52  51.08
2022-03-02  27.31  50.82
2022-03-03  26.76  50.12
2022-03-04  26.50  49.80

loc 标签索引:布尔条件筛选

Listing 5: 使用loc进行布尔条件筛选
展开实现代码
# 布尔条件筛选
print('三一重工价格>20的交易日:')
syzg_high = df.loc[df.syzg > 20]
print(syzg_high.head(3))
三一重工价格>20的交易日:
             syzg   zgpa  zgjz
date                          
2022-03-01  27.52  51.08  5.39
2022-03-02  27.31  50.82  5.35
2022-03-03  26.76  50.12  5.30
  • df.syzg > 20 返回布尔 Series
  • loc 用这个布尔 Series 来筛选行

iloc 位置索引:基于整数位置

  • iloc 使用整数位置进行选择
  • 切片不包含末端(标准 Python 切片规则)
Listing 6: 使用iloc进行位置索引
展开实现代码
# 前2行前2列
print('前2行前2列:')
print(df.iloc[:2, :2])

# 特定位置的单个值
print(f'\n第1行第1列: {df.iloc[0, 0]}')

# 负索引(倒数第3行、第1列)
print(f'\n倒数第3行第1列: {df.iloc[-3, 0]}')
前2行前2列:
             syzg   zgpa
date                    
2022-03-01  27.52  51.08
2022-03-02  27.31  50.82

第1行第1列: 27.52

倒数第3行第1列: 25.54

loc 与 iloc 对比总结

特性 loc iloc
索引方式 标签(行名/列名) 整数位置
切片末端 包含末端 不包含末端
适用场景 按日期/列名查询 按位置批量提取
示例 df.loc['2022-03-01'] df.iloc[0]

sort_index:按索引排序

Listing 7: 按索引排序DataFrame
展开实现代码
# 升序排序(从早到晚)
df_asc = df.sort_index(ascending=True)
print('升序排序(日期从早到晚):')
print(df_asc.head(3))

# 降序排序(从晚到早)
df_desc = df.sort_index(ascending=False)
print(f'\n降序排序(日期从晚到早):')
print(df_desc.head(2))
升序排序(日期从早到晚):
             syzg   zgpa  zgjz
date                          
2022-03-01  27.52  51.08  5.39
2022-03-02  27.31  50.82  5.35
2022-03-03  26.76  50.12  5.30

降序排序(日期从晚到早):
             syzg   zgpa  zgjz
date                          
2022-03-10  25.35  48.41  5.15
2022-03-09  24.92  47.90  5.09
  • 时间序列分析的前提:确保数据按时间顺序排列

sort_values:按列值排序

Listing 8: 按列值排序DataFrame
# 按单列排序(找出最高价交易日)
print('按三一重工价格降序:')
df_sorted = df.sort_values('syzg', ascending=False)
print(df_sorted.head())
按三一重工价格降序:
             syzg   zgpa  zgjz
date                          
2022-03-01  27.52  51.08  5.39
2022-03-02  27.31  50.82  5.35
2022-03-03  26.76  50.12  5.30
2022-03-04  26.50  49.80  5.28
2022-03-07  25.98  48.95  5.20

sort_values:多列排序

Listing 9: 按多列值排序DataFrame
# 先按syzg升序, 再按zgpa降序
print('先按syzg升序, 再按zgpa降序:')
df_multi_sort = df.sort_values(
    ['syzg', 'zgpa'],
    ascending=[True, False]
)
print(df_multi_sort.head())
先按syzg升序, 再按zgpa降序:
             syzg   zgpa  zgjz
date                          
2022-03-09  24.92  47.90  5.09
2022-03-10  25.35  48.41  5.15
2022-03-08  25.54  48.36  5.14
2022-03-07  25.98  48.95  5.20
2022-03-04  26.50  49.80  5.28
  • 第一排序键相同时,才看第二排序键

条件筛选:单条件

Listing 10: 单条件筛选DataFrame
# 单条件筛选
print('三一重工>20:')
print(df[df.syzg > 20].head())
三一重工>20:
             syzg   zgpa  zgjz
date                          
2022-03-01  27.52  51.08  5.39
2022-03-02  27.31  50.82  5.35
2022-03-03  26.76  50.12  5.30
2022-03-04  26.50  49.80  5.28
2022-03-07  25.98  48.95  5.20
  • df.syzg > 20 返回布尔 Series
  • 用布尔 Series 作为索引,筛选出满足条件的行

条件筛选:多条件组合

Listing 11: 多条件筛选DataFrame
展开实现代码
# AND 条件:两个条件同时满足
print('三一重工>20 且 中国平安>40:')
print(df[(df.syzg > 20) & (df.zgpa > 40)].head(2))

# OR 条件:满足其中一个即可
print(f'\n三一重工>30 或 中国平安>45:')
print(df[(df.syzg > 30) | (df.zgpa > 45)].head(2))
三一重工>20 且 中国平安>40:
             syzg   zgpa  zgjz
date                          
2022-03-01  27.52  51.08  5.39
2022-03-02  27.31  50.82  5.35

三一重工>30 或 中国平安>45:
             syzg   zgpa  zgjz
date                          
2022-03-01  27.52  51.08  5.39
2022-03-02  27.31  50.82  5.35

多条件筛选:组合规则

  • & 逻辑与(AND),| 逻辑或(OR)
  • 每个条件必须加括号

条件筛选:isin 成员检查

Listing 12: 使用isin进行成员检查
# 选择特定日期的数据
dates_to_check = ['2022-03-01', '2022-03-02', '2022-03-03']
print('特定日期的数据:')
print(df.loc[dates_to_check])
特定日期的数据:
             syzg   zgpa  zgjz
date                          
2022-03-01  27.52  51.08  5.39
2022-03-02  27.31  50.82  5.35
2022-03-03  26.76  50.12  5.30
  • 等价于 df[df.index.isin(dates_to_check)]

query 方法:更直观的查询语法

Listing 13: 使用query方法筛选数据
展开实现代码
# 简单条件(类似SQL语法)
result1 = df.query('syzg > 20')
print('syzg > 20:')
print(result1.head())

# 多条件(使用 and/or/not)
result2 = df.query('syzg > 20 and zgpa < 40')
print(f'\nsyzg > 20 and zgpa < 40:')
print(result2.head())
syzg > 20:
             syzg   zgpa  zgjz
date                          
2022-03-01  27.52  51.08  5.39
2022-03-02  27.31  50.82  5.35
2022-03-03  26.76  50.12  5.30
2022-03-04  26.50  49.80  5.28
2022-03-07  25.98  48.95  5.20

syzg > 20 and zgpa < 40:
Empty DataFrame
Columns: [syzg, zgpa, zgjz]
Index: []

query 方法:引用外部变量

Listing 14: 在query中引用外部变量
# 使用@引用外部变量
threshold = 25
result3 = df.query('syzg > @threshold')
print(f'syzg > {threshold}:')
print(result3.head())
syzg > 25:
             syzg   zgpa  zgjz
date                          
2022-03-01  27.52  51.08  5.39
2022-03-02  27.31  50.82  5.35
2022-03-03  26.76  50.12  5.30
2022-03-04  26.50  49.80  5.28
2022-03-07  25.98  48.95  5.20
  • @变量名:在 query 中引用 Python 环境变量
  • 优势:语法更简洁,可读性更高

apply:对行或列应用函数

Listing 15: 使用apply对行或列应用函数
展开实现代码
# 对每列应用函数(计算极差)
print('每列的极差(最高价-最低价):')
print(df.apply(lambda x: x.max() - x.min()))

# 对每行应用函数(计算均值)
print(f'\n每行的均值(每日三只股票平均):')
print(df.apply(lambda x: x.mean(), axis=1).head(2))
每列的极差(最高价-最低价):
syzg    2.60
zgpa    3.18
zgjz    0.30
dtype: float64

每行的均值(每日三只股票平均):
date
2022-03-01    27.996667
2022-03-02    27.826667
dtype: float64

apply:选择操作方向

  • axis=0(默认):沿列方向操作
  • axis=1:沿行方向操作(跨列计算)

map:对 Series 逐元素变换

Listing 16: 使用map进行逐元素变换
展开实现代码
# 定义价格分类函数
def categorize_price(price):
    if price > 30:
        return '高'
    elif price > 20:
        return '中'
    else:
        return '低'

# 对syzg列逐元素应用
syzg_category = df['syzg'].map(categorize_price)
print('三一重工价格分类(前10个):')
print(syzg_category.head(10))
三一重工价格分类(前10个):
date
2022-03-01    中
2022-03-02    中
2022-03-03    中
2022-03-04    中
2022-03-07    中
2022-03-08    中
2022-03-09    中
2022-03-10    中
Name: syzg, dtype: object
  • map 只用于 Series,对每个元素逐个应用函数

apply 与 map 对比总结

特性 apply map
适用对象 DataFrame 或 Series 仅 Series
操作维度 按行或按列 逐元素
常见用途 聚合统计、跨列计算 值替换、分类映射
示例 df.apply(func, axis=1) series.map(func)

本章小结

  • 数据读取read_csvindex_colusecols 等参数
  • 标签索引loc 按标签选择,切片包含末端
  • 位置索引iloc 按整数位置,切片不含末端
  • 排序sort_index 按索引,sort_values 按值
  • 筛选:布尔条件(&/|)与 query 方法
  • 变换apply 按行/列操作,map 逐元素操作

随堂练习

  • 问题 1|需要准备哪些数据?:含代码、日期、价格和行业字段的 DataFrame。
  • 问题 2|需要完成哪些操作?:运行 lst-sort-valueslst-apply-demolst-map-demo,完成排序、逐行计算和逐元素映射。
  • 问题 3|应得到哪些结果?:排序后的键序、apply 新列与 map 标签列。
  • 问题 4|怎样确认结果可靠?:用显式表达式复算一行并确认排序单调;索引错位或 dtype 不符时先检查原因。
  • 问题 5|换一个情境,怎样继续应用?:把操作步骤应用到另一份中国公司明细。
  • 作答提示:请依次写清所用数据、分析过程、所得结果、核对方法和拓展思考。课程所需数据见前言中的下载入口;教学平台固定题按页面说明完成。

教师参考解答|答案与说明 1

  • 补充练习:下方结果须先以 sort_values('revenue', ascending=False) 得到相同的排序结果,再比较 map 的单列值映射与 apply(axis=1) 的多列行逻辑;拓展应用到长三角同季财报时用单调性、键集合与行数三项核对,未知状态不得静默填值。

  • 所用数据与字段:表含 code、revenue

  • 参考代码或推导(平台保护块之外):

教师参考解答|代码 1

展开代码(代码区可独立滚动)
import pandas as pd  # 导入表格库
df=pd.DataFrame({'code':['A','A','B'],'revenue':[3,1,2]})  # 创建输入
status_map={'A':'重点','B':'观察'}  # 定义单列值映射
answer=df.assign(status=df['code'].map(status_map))  # 用map逐元素映射代码
answer['scaled']=answer.apply(lambda row:row['revenue']*2 if row['status']=='重点' else row['revenue'],axis=1)  # 用apply执行多列行逻辑
answer=answer.sort_values('revenue',ascending=False)  # 按收入降序得到相同的排序结果
print(answer)  # 输出结果

教师参考解答|答案与说明 2

  • 参考结果:保留3行并新增 status、scaled;revenue 按 3、2、1 降序
  • 边界 / 局限:链式操作后仍核对行数和键
  • 常见错误:覆盖原列;排序方向错误